package memoizer;

import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Memoizer<A, V> implements Computable<A, V>{
    private final Computable<A, V> computer;
    private final ConcurrentHashMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
    private final ConcurrentHashMap<A, Long> cacheComputeStartMillis = new ConcurrentHashMap<A, Long>();
    private final long millisLive;
    
    public Memoizer(Computable<A, V> computer, long millisLive) {
        this.computer = computer;
        this.millisLive = millisLive;
    }

    @Override
    public V compute(final A arg) throws InterruptedException {
        while (true) {
            Long computeStartMillis = cacheComputeStartMillis.get(arg);
            if (computeStartMillis != null && 
                    System.currentTimeMillis() - computeStartMillis > millisLive) {
                //время начала вычислений зафиксировано и слишком позднее
                cache.remove(arg);
            }
            Future<V> future = cache.get(arg);
            if (future == null) {
                Callable<V> eval = new Callable<V>() {

                    @Override
                    public V call() throws Exception {
                        V value = computer.compute(arg); 
                        cacheComputeStartMillis.put(arg, System.currentTimeMillis());
                        return value;
                    }
                };
                
                FutureTask<V> ft = new FutureTask<V>(eval);
                future = cache.putIfAbsent(arg, future);
                if (future == null) {
                    future = ft;
                    ft.run();
                }
            } 
            try {
                return future.get();
            } catch (CancellationException e) {
                cache.remove(arg);
            } catch (ExecutionException e) {
                cache.remove(arg);
                throw new RuntimeException(e);
            }
        }    
    }
}

class ExpensiveCompuatation implements Computable<String, BigInteger> {

    @Override
    public BigInteger compute(String arg) throws InterruptedException {
        return new BigInteger(arg);
    }
    
}